package snapex.user.controller;

import java.util.HashMap;
import java.util.Map;

import org.jasypt.encryption.StringEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.micrometer.core.instrument.util.StringUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import snapex.core.SnapexApplicationException;
import snapex.core.ValidationFailureException;
import snapex.core.model.MyOAuth2AccessToken;
import snapex.core.model.Result;
import snapex.core.model.User;
import snapex.core.model.WechatAccessToken;
import snapex.core.model.WechatAuthToken;
import snapex.core.security.GatewayTokenServiceProxy;
import snapex.user.service.IUserService;


@Slf4j
@Controller
public class UserRegisterController {

	@Autowired
	IUserService userService;
	
	@Autowired @Qualifier("jasyptStringEncryptor")
	StringEncryptor stringEncryptor;
	
	@Autowired
	GatewayTokenServiceProxy gatewayTokenServiceProxy;
	
	@Value("${snapex.wechat.enabled}")
	boolean wechatEnabled;
	
	@Value("${snapex.wechat.authUrl}")
	String authUrl;
	
	@Value("${snapex.wechat.appId}")
	String appId;
	
	@Value("${snapex.wechat.appSecret}")
	String appSecret;
		
	@Value("${snapex.wechat.accessTokenApi}")
	String accessTokenApi;
	
	@Value("${snapex.wechat.wxacodeApi}")
	String wxacodeApi;
	
	@Value("${snapex.wechat.wxaPage}")
	String wxaPage;
	
	@Value("${snapex.wechat.expirySeconds}")
	int expirySeconds;
	
	 @Autowired
	 private RestTemplate restTemplate;
	 
	 private static final ObjectMapper MAPPER = new ObjectMapper();
	
	@ApiOperation("Register using wechat QR Code")
	@GetMapping(value = "/user/register", produces = MediaType.TEXT_HTML_VALUE)	
	public String register(@RequestParam("hashKey") String hashUserId, ModelMap modelMap)  throws Exception{		

		modelMap.put("hashKey", hashUserId);

		return "logonQRCode";
	}

	@ApiOperation(value = "logon QR code which carries the hash value of username")
	@GetMapping(value = "/user/wxacode", produces = MediaType.IMAGE_JPEG_VALUE)
	public ResponseEntity<byte[]> miniProgramLogonCode(@RequestParam("hashKey") String hashKeyTimestamp) throws Exception {
		String hashKey = decodeHashKey(hashKeyTimestamp);
		//get existing code from storage
		byte[] code = userService.getCodeByHashKey(hashKey);
		if(code == null) {
			code = getWxacode(hashKey);
			userService.saveCodeByHashKey(hashKey, code);
		}
		return ResponseEntity.ok(code);
	}

	private byte[] getWxacode(String hashKey) {
		//generate new code
		WechatAccessToken token = restTemplate.getForEntity(String.format(accessTokenApi,appId,appSecret), WechatAccessToken.class).getBody();
				
		String scene = String.format("k=%s",hashKey);
		log.debug("*********Generate Wxacode for scene:{}",scene);
		//获取小程序码调用API
        Map<String,Object> param = new HashMap();        
        param.put("page", wxaPage);//小程序页面
        param.put("width", 280);
        param.put("scene", scene);//参数
        param.put("auto_color", true);
        
        HttpHeaders headers = new HttpHeaders();
        HttpEntity requestEntity = new HttpEntity(param, headers);
        ResponseEntity<byte[]> code = restTemplate.exchange(String.format(wxacodeApi, token.getAccess_token()), HttpMethod.POST, requestEntity, byte[].class, new Object[0]);
        
        return code.getBody();
	}
	
	private String decodeHashKey(String hashKeyTimestamp) {
		log.debug("*****hashKeyTimestamp:{}",hashKeyTimestamp);				
		//hashKey = hashKey.substring(0, 10) + System.currentTimeMillis() + hashKey.substring(10) 
			
		String hashKey = hashKeyTimestamp.substring(0, 10) + hashKeyTimestamp.substring(10 + "1234567890ABC".length());
		String timestamp = hashKeyTimestamp.substring(10, 10 + "1234567890ABC".length());
				
		log.debug("*****hashKey:{}, timestamp:{}",hashKey, timestamp);
		
		if (System.currentTimeMillis() - Long.valueOf(timestamp).longValue() > expirySeconds * 1000) {
			throw new SnapexApplicationException("Session expired");
		}
		
		return hashKey;
	}
	
	private WechatAuthToken getWechatAuthToken(String code) throws Exception{
		if(StringUtils.isEmpty(code)){
			throw new ValidationFailureException("code is missing");
		}
		WechatAuthToken wechatToken = new WechatAuthToken();
		if(wechatEnabled) {
			String response = restTemplate.getForObject(String.format(authUrl, appId,appSecret, code), String.class);		
			wechatToken = MAPPER.readValue(response, WechatAuthToken.class);
		}else {
			wechatToken.setOpenid("DMY-openid");
		}
		
		log.debug("****WechatAccessToken:{}", wechatToken);
		
		return wechatToken;
	}
	//String params = String.format("userId=%s", hashKey.substring(0, 10) + System.currentTimeMillis() + hashKey.substring(10));
	
	@PostMapping(value = "/user/register",consumes=MediaType.APPLICATION_JSON_UTF8_VALUE, produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
	@ResponseBody
	public ResponseEntity<Object> registerHashKey(@RequestBody User user) throws Exception{
		
		WechatAuthToken wechatToken = getWechatAuthToken(user.getCode());
		
		if(!StringUtils.isEmpty(wechatToken.getOpenid())) {
			user.setOpenId(wechatToken.getOpenid());
		}else {
			return ResponseEntity.ok(wechatToken);
		}
		
		//get access token by openid for users already bound with wechat
		MyOAuth2AccessToken accessToken = gatewayTokenServiceProxy.getAccessTokenByOpenId(user.getOpenId());
		
		if(accessToken == null) {
			String hashUsernameTimestamp = user.getHashKey();
				
			if(StringUtils.isEmpty(hashUsernameTimestamp)){
				throw new ValidationFailureException("hashKey is missing");
			}
			//register new user 
			String wechatKey = userService.register(decodeHashKey(hashUsernameTimestamp));
			
			//registered user already has wechatKey
			if(!StringUtils.isEmpty(wechatKey)){			
				accessToken = gatewayTokenServiceProxy.getAccessTokenByHashKey(wechatKey);
				//accessToken = gatewayTokenServiceProxy.getAccessToken(user.getUserId(),user.getPasscode(),true);
				if(accessToken == null) {
					wechatToken.setErrcode(401);
					wechatToken.setErrmsg("Bad Credentials");
					return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(wechatToken);
				}
			}
			
			//bind openid and wechatKey
			user = userService.registerWechatOpenId(wechatKey,user.getOpenId());			
		}
		
		Map<String,Object> token = new HashMap<String,Object>();
		//token.put(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getAccess_token());
		token.put("WechatAccessToken", accessToken.getAccess_token());
		token.put("ExpiryInSeconds", accessToken.getExpires_in());
		return ResponseEntity.ok(token);
	}
//	public String testredirect(RedirectAttributes attr) {
//		attr.addAttribute("test", "51gjie");//跳转地址带上test参数
//	    attr.addFlashAttribute("u2", "51gjie");//跳转地址不带上u2参数
//		return "redirect:/user/users";
//	}	
	@ApiOperation("Logout")
	@PostMapping(value = "/user/logout", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)	
	public ResponseEntity<Result> logout()  throws Exception{
		
		if(userService.logout(username())) {
			return ResponseEntity.ok(Result.success());
		}
		
		return ResponseEntity.ok(Result.failure());
	}
	
	private String username() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();	
		return authentication.getName();
	}
}
